#include "./fsm.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>

#define set_fl(fd, fl)                           \
    if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) \
    {                                            \
        perror("fcntl fset fl");                 \
        exit(1);                                 \
    }

struct fsm_str
{
    int state;
    int src;
    int dst;
    int src_flags;
    int dst_flags;
    char buf[BUF_SIZE];
    ssize_t buf_len;
    char *cursor;
    char *error_msg;
};

void fsm_drive(fsm_st p)
{
    struct fsm_str *fsm = p;
    switch (fsm->state)
    {
    case STATE_R:
    {
        // read content into buffer
        ssize_t len = read(fsm->src, fsm->buf, BUF_SIZE);
        if (len < 0)
        {
            // error occurred, if error is err int or err nonblock, do nothing
            if (errno == EAGAIN)
            {
                break;
            }
            fsm->state = STATE_EX;
            fsm->error_msg = strerror(errno);
            break;
        }
        if (len == 0)
        {
            // terminated
            fsm->state = STATE_T;
            break;
        }

        fsm->buf_len = len;
        fsm->state = STATE_W;
        fsm->cursor = fsm->buf;
        break;
    }

    case STATE_W:
    {
        ssize_t len = write(fsm->dst, fsm->cursor, fsm->buf_len);
        if (len < 0)
        {
            // error occurred, if error is err int or err nonblock, do nothing
            if (errno == EAGAIN)
            {
                break;
            }
            fsm->state = STATE_EX;
            fsm->error_msg = strerror(errno);
            break;
        }

        fsm->buf_len -= len;
        fsm->cursor += len;
        if (fsm->buf_len == 0)
        {
            fsm->state = STATE_R;
        }
        break;
    }
    case STATE_EX:
    {
        fsm->state = STATE_T;
        break;
    }

    default:
        break;
    }
}

fsm_st fsm_new(int src, int dst)
{
    struct fsm_str *fsm = malloc(sizeof(struct fsm_str));
    if (fsm == NULL)
        return fsm;
    fsm->src = src;
    fsm->src_flags = fcntl(src, F_GETFL);
    set_fl(src, fsm->src_flags | O_NONBLOCK);
    fsm->dst = dst;
    fsm->dst_flags = fcntl(dst, F_GETFL);
    set_fl(dst, fsm->dst_flags | O_NONBLOCK);
    fsm->state = STATE_R;
    fsm->error_msg = NULL;
    return (void *)fsm;
}

void fsm_close(fsm_st p)
{
    struct fsm_str *fsm = p;
    set_fl(fsm->src, fsm->src_flags);
    set_fl(fsm->dst, fsm->dst_flags);
    free(fsm);
}

int fsm_state(fsm_st p)
{
    struct fsm_str *fsm = p;
    return fsm->state;
}

const char *fsm_error(fsm_st p)
{
    struct fsm_str *fsm = p;
    return fsm->error_msg;
}